package cc.gpai.data_stru.multimap;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import cc.gpai.data_stru.multimap.impl.EntryImpl;

public abstract class AbstractHiDMultiMap<K, V> implements HiDMultiMap<K, V> {

	protected final Map<List<K>, Collection<V>> map = initMap();

	/**
	 * Create a new <code>Collection</code> for the given <code>key</code> that
	 * contains all elements of <code>key</code>. It is <b>discouraged</b> to
	 * return the parameter as is if the parameter is to be changed.
	 * 
	 * @param col
	 * @return
	 */
	protected abstract List<K> newKeyList(List<K> key);

	/**
	 * Create a new <code>Collection</code> for the given <code>col</code> that
	 * contains all elements of <code>col</code>. It is <b>discouraged</b> to
	 * return the parameter as is if either the parameter or the value of a
	 * given key of this is map is to be changed.
	 * 
	 * @param col
	 * @return
	 */
	protected abstract Collection<V> newValueCollection(Collection<V> col);

	protected abstract Map<List<K>, Collection<V>> initMap();

	@SuppressWarnings("unchecked")
	private boolean ensureKey(List<K> key) {
		if (keys().contains(key)) {
			return false;
		} else {
			map.put(newKeyList(key), newValueCollection(Arrays.<V> asList()));
			return true;
		}
	}

	@Override
	public Collection<V> getValues(List<K> key) {
		return map.get(key);
	}

	@Override
	public Collection<V> getValues(K... key) {
		return getValues(Arrays.asList(key));
	}

	@Override
	public Collection<V> put(Collection<V> value, K... key) {
		return put(Arrays.asList(key), value);
	}

	@Override
	public Collection<V> put(K[] key, V... value) {
		return put(Arrays.asList(key), Arrays.asList(value));
	}

	@Override
	public Collection<V> put(K[] key, Collection<V> value) {
		return put(Arrays.asList(key), value);
	}

	@Override
	public Collection<V> put(List<K> key, Collection<V> value) {
		ensureKey(key);
		Collection<V> col = newValueCollection(map.get(key));
		map.get(key).addAll(value);
		return col;
	}

	@Override
	public Collection<V> getValues(Object key) {
		return map.get(Arrays.asList(key));
	}

	@Override
	public Collection<V> putValues(K key, Collection<V> value) {
		return put(Collections.singletonList(key), value);
	}

	@Override
	public boolean remove(K key, V value) {
		Collection<V> col = getValues(key);
		if (col == null || col.isEmpty()) {
			return false;
		} else {
			return col.remove(value);
		}
	}

	@Override
	public boolean removeKey(K key) {
		List<K> l = Collections.singletonList(key);
		boolean b = map.containsKey(l);
		map.remove(l);
		return b;
	}

	@Override
	public boolean removeValue(V value) {
		boolean b = false;
		for (Collection<V> col : map.values()) {
			b |= col.remove(value);
		}
		return b;
	}

	@Override
	public boolean containsKey(Object key) {
		return map.containsKey(Collections.singletonList(key));
	}

	@Override
	public boolean containsValue(Object value) {
		for (Collection<V> col : map.values()) {
			if (col.contains(value)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public Set<Entry<K, V>> entrySet() {
		Set<Entry<K, V>> set = new HashSet<Entry<K, V>>();
		for (Entry<List<K>, Collection<V>> e : map.entrySet()) {
			for (K k : e.getKey()) {
				for (V v : e.getValue()) {
					set.add(new EntryImpl<K, V>(k, v));
				}
			}
		}
		return set;
	}

	@Override
	public V get(Object key) {
		Collection<V> col = map.get(Arrays.asList(key));
		if (col == null || col.isEmpty()) {
			return null;
		} else {
			return col.iterator().next();
		}
	}

	@Override
	public Set<K> keySet() {
		Set<K> set = new HashSet<K>();
		for (List<K> l : map.keySet()) {
			set.addAll(l);
		}
		return set;
	}

	@Override
	public V put(K key, V value) {
		return put(Collections.singletonList(key), value);
	}

	@Override
	public void putAll(Map<? extends K, ? extends V> m) {
		for (Entry<? extends K, ? extends V> e : m.entrySet()) {
			put(e.getKey(), e.getValue());
		}
	}

	@Override
	public V remove(Object key) {
		Collection<V> col = map.remove(Arrays.asList(key));
		if (col == null || col.isEmpty()) {
			return null;
		} else {
			return col.iterator().next();
		}
	}

	@Override
	public Collection<V> values() {
		@SuppressWarnings("unchecked")
		Collection<V> col = newValueCollection(Arrays.<V> asList());
		for (Collection<V> c : map.values()) {
			col.addAll(c);
		}
		return col;
	}

	@Override
	public boolean containsKey(K... key) {
		return map.containsKey(Arrays.asList(key));
	}

	@Override
	public V get(K... key) {
		Collection<V> col = map.get(Arrays.asList(key));
		if (col == null || col.isEmpty()) {
			return null;
		} else {
			return col.iterator().next();
		}
	}

	@Override
	public Set<List<K>> keys() {
		return map.keySet();
	}

	@Override
	public V put(V value, K... key) {
		return put(Arrays.asList(key), value);
	}

	@Override
	public V put(K[] key, V value) {
		return put(Arrays.asList(key), value);
	}

	@Override
	public V put(List<K> key, V value) {
		Collection<V> col = put(key, Collections.singletonList(value));
		if (col == null || col.isEmpty()) {
			return null;
		} else {
			return col.iterator().next();
		}
	}

	public int size() {
		return map.size();
	}

	public boolean isEmpty() {
		return map.isEmpty();
	}

	public void clear() {
		map.clear();
	}

	@Override
	public int hashCode() {
		return (map == null) ? 0 : map.hashCode();
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		@SuppressWarnings("rawtypes")
		AbstractHiDMultiMap other = (AbstractHiDMultiMap) obj;
		if (map == null) {
			if (other.map != null)
				return false;
		} else if (!map.equals(other.map))
			return false;
		return true;
	}

	@Override
	public String toString() {
		return String.valueOf(map);
	}
}
